// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package escape

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/src"
)

// call计算调用表达式，包括内置调用。ks 
// 应该包含表示函数被调用方的
// 结果流向的孔。
func (e *escape) call(ks []hole, call ir.Node) {
	var init ir.Nodes
	e.callCommon(ks, call, &init, nil)
	if len(init) != 0 {
		call.(*ir.CallExpr).PtrInit().Append(init...)
	}
}

func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir.Func) {

	// argumentPragma处理参数*argp到给定洞的转义分析。如果函数被调用方已知，则pragma是
	// 函数的pragma标志；否则0。
	argumentFunc := func(fn *ir.Name, k hole, argp *ir.Node) {
		e.rewriteArgument(argp, init, call, fn, wrapper)

		e.expr(k.note(call, "call parameter"), *argp)
	}

	argument := func(k hole, argp *ir.Node) {
		argumentFunc(nil, k, argp)
	}

	switch call.Op() {
	default:
		ir.Dump("esc", call)
		base.Fatalf("unexpected call op: %v", call.Op())

	case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
		call := call.(*ir.CallExpr)
		typecheck.FixVariadicCall(call)
		typecheck.FixMethodCall(call)

		// 如果静态已知，请选择被调用函数。
		// 
		// TODO（mdempsky）：将fn从*ir更改为fn。我的名字是*ir。Func，但一些
		// 函数（例如，运行时内置函数、方法包装器、生成的
		// eq/哈希函数）没有设置它。调查
		// 这是否是一个问题。
		var fn *ir.Name
		switch call.Op() {
		case ir.OCALLFUNC:
			// 如果我们直接调用了一个闭包（不仅仅是一个，我们是
			// 能够用ir.StaticValue静态解析），请将其标记为
			// 这样的批处理。大纲可以优化流程结果。
			if call.X.Op() == ir.OCLOSURE {
				call.X.(*ir.ClosureExpr).Func.SetClosureCalled(true)
			}

			switch v := ir.StaticValue(call.X); v.Op() {
			case ir.ONAME:
				if v := v.(*ir.Name); v.Class == ir.PFUNC {
					fn = v
				}
			case ir.OCLOSURE:
				fn = v.(*ir.ClosureExpr).Func.Nname
			case ir.OMETHEXPR:
				fn = ir.MethodExprName(v)
			}
		case ir.OCALLMETH:
			base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
		}

		fntype := call.X.Type()
		if fn != nil {
			fntype = fn.Type()
		}

		if ks != nil && fn != nil && e.inMutualBatch(fn) {
			for i, result := range fn.Type().Results().FieldSlice() {
				e.expr(ks[i], ir.AsNode(result.Nname))
			}
		}

		var recvp *ir.Node
		if call.Op() == ir.OCALLFUNC {
			// 计算被调用函数表达式。
			// 
			// 注意：我们使用参数而不是argumentFunc，因为虽然
			// 调用。这里的X可能是运行时的一个参数。{new，defer}proc，
			// 这不是fn本身的论点。
			argument(e.discardHole(), &call.X)
		} else {
			recvp = &call.X.(*ir.SelectorExpr).X
		}

		args := call.Args
		if recv := fntype.Recv(); recv != nil {
			if recvp == nil {
				// 使用方法表达式调用函数。receivier参数是
				// 位于常规参数列表的前面。
				recvp = &args[0]
				args = args[1:]
			}

			argumentFunc(fn, e.tagHole(ks, fn, recv), recvp)
		}

		for i, param := range fntype.Params().FieldSlice() {
			argumentFunc(fn, e.tagHole(ks, fn, param), &args[i])
		}

	case ir.OINLCALL:
		call := call.(*ir.InlinedCallExpr)
		e.stmts(call.Body)
		for i, result := range call.ReturnVars {
			k := e.discardHole()
			if ks != nil {
				k = ks[i]
			}
			e.expr(k, result)
		}

	case ir.OAPPEND:
		call := call.(*ir.CallExpr)
		args := call.Args

		// 如果
		// 它有足够的容量，则附件切片可能会直接流向结果。或者，可能会分配一个新的堆
		// 切片，所有切片元素
		// 可能会流到堆中。
		appendeeK := ks[0]
		if args[0].Type().Elem().HasPointers() {
			appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice"))
		}
		argument(appendeeK, &args[0])

		if call.IsDDD {
			appendedK := e.discardHole()
			if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() {
				appendedK = e.heapHole().deref(call, "appended slice...")
			}
			argument(appendedK, &args[1])
		} else {
			for i := 1; i < len(args); i++ {
				argument(e.heapHole(), &args[i])
			}
		}

	case ir.OCOPY:
		call := call.(*ir.BinaryExpr)
		argument(e.discardHole(), &call.X)

		copiedK := e.discardHole()
		if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() {
			copiedK = e.heapHole().deref(call, "copied slice")
		}
		argument(copiedK, &call.Y)

	case ir.OPANIC:
		call := call.(*ir.UnaryExpr)
		argument(e.heapHole(), &call.X)

	case ir.OCOMPLEX:
		call := call.(*ir.BinaryExpr)
		argument(e.discardHole(), &call.X)
		argument(e.discardHole(), &call.Y)

	case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
		call := call.(*ir.CallExpr)
		fixRecoverCall(call)
		for i := range call.Args {
			argument(e.discardHole(), &call.Args[i])
		}

	case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
		call := call.(*ir.UnaryExpr)
		argument(e.discardHole(), &call.X)

	case ir.OUNSAFEADD, ir.OUNSAFESLICE:
		call := call.(*ir.BinaryExpr)
		argument(ks[0], &call.X)
		argument(e.discardHole(), &call.Y)
	}
}

// goDeferStmt分析“go”或“defer”语句。
// 
// 在这个过程中，它还规范化了语句，始终使用
// 简单函数调用，没有参数和结果。例如，
// 它重写了：
// 
// 延迟f（x，y）
// 
// 转换为：
// 
// x1，y1:=x，y 
// 延迟函数{f（x1，y1）}
func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
	k := e.heapHole()
	if n.Op() == ir.ODEFER && e.loopDepth == 1 {
		k = e.later(e.discardHole())

		// 强制延迟记录的堆栈分配，除非
		// 使用开放编码延迟（参见ssa.go）
		n.SetEsc(ir.EscNever)
	}

	call := n.Call

	init := n.PtrInit()
	init.Append(ir.TakeInit(call)...)
	e.stmts(*init)

	// 如果函数已经是一个零参数/结果函数调用，
	// 只需正常转义分析即可。
	if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
		if sig := call.X.Type(); sig.NumParams()+sig.NumResults() == 0 {
			if clo, ok := call.X.(*ir.ClosureExpr); ok && n.Op() == ir.OGO {
				clo.IsGoWrap = true
			}
			e.expr(k, call.X)
			return
		}
	}

	// 创建一个新的无参数函数，我们将把它交给defer。
	fn := ir.NewClosureFunc(n.Pos(), true)
	fn.SetWrapper(true)
	fn.Nname.SetType(types.NewSignature(types.LocalPkg, nil, nil, nil, nil))
	fn.Body = []ir.Node{call}
	if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
		// 如果被调用方是命名函数，请链接到原始被调用方。
		x := call.X
		if x.Op() == ir.ONAME && x.(*ir.Name).Class == ir.PFUNC {
			fn.WrappedFunc = call.X.(*ir.Name).Func
		} else if x.Op() == ir.OMETHEXPR && ir.MethodExprFunc(x).Nname != nil {
			fn.WrappedFunc = ir.MethodExprName(x).Func
		}
	}

	clo := fn.OClosure
	if n.Op() == ir.OGO {
		clo.IsGoWrap = true
	}

	e.callCommon(nil, call, init, fn)
	e.closures = append(e.closures, closure{e.spill(k, clo), clo})

	// 创建新的顶级关闭调用。
	n.Call = ir.NewCallExpr(call.Pos(), ir.OCALL, clo, nil)
	ir.WithFunc(e.curfn, func() {
		typecheck.Stmt(n.Call)
	})
}

// rewriteArgument重写给定调用表达式的参数*argp。
// fn是静态被调用函数（如果已知）。
// wrapper是用于调用的go/defer包装函数（如果有）。
func (e *escape) rewriteArgument(argp *ir.Node, init *ir.Nodes, call ir.Node, fn *ir.Name, wrapper *ir.Func) {
	var pragma ir.PragmaFlag
	if fn != nil && fn.Func != nil {
		pragma = fn.Func.Pragma
	}

	// unsafeUintptr将“uintptr（ptr）”参数重写为类似于
	// 函数的syscall，以便ptr保持活动状态和/或作为
	// 适当的转义。unsafeUintptr还报告它是否修改了arg0。
	unsafeUintptr := func(arg0 ir.Node) bool {
		if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 {
			return false
		}

		// 如果参数真的是一个正在转换为uintptr的指针，则
		// 安排该指针保持活动状态，直到调用返回，
		// 将其复制到一个临时文件中，并在弹出临时堆栈时标记该临时文件
		// 仍然活动。
		if arg0.Op() != ir.OCONVNOP || !arg0.Type().IsUintptr() {
			return false
		}
		arg := arg0.(*ir.ConvExpr)

		if !arg.X.Type().IsUnsafePtr() {
			return false
		}

		// 创建并声明一个新的指针类型的临时变量。
		tmp := e.wrapExpr(arg.Pos(), &arg.X, init, call, wrapper)

		if pragma&ir.UintptrEscapes != 0 {
			e.flow(e.heapHole().note(arg, "// go:uintTrescapes”），e.oldLoc（tmp））
		}

		if pragma&ir.UintptrKeepAlive != 0 {
			call := call.(*ir.CallExpr)

			// SSA使用OpVarLive实现CallExpr.KeepAlive，它不支持PAUTOHEAP变量。我尝试将其更改为
			// use OpKeepAlive，但它本身遇到了问题。
			// 目前，简单的解决方案是显式复制到（还有另一个）新的临时变量。
			keep := tmp
			if keep.Class == ir.PAUTOHEAP {
				keep = e.copyExpr(arg.Pos(), tmp, call.PtrInit(), wrapper, false)
			}

			keep.SetAddrtaken(true) // 确保SSA保留tmp变量
			call.KeepAlive = append(call.KeepAlive, keep)
		}

		return true
	}

	visit := func(pos src.XPos, argp *ir.Node) {
		// 优化几个常见的常量表达式。通过在调用表达式中保持这些
		// 不变，我们让包装器处理
		// 对它们进行评估，而不是占用闭包上下文空间。
		switch arg := *argp; arg.Op() {
		case ir.OLITERAL, ir.ONIL, ir.OMETHEXPR:
			return
		case ir.ONAME:
			if arg.(*ir.Name).Class == ir.PFUNC {
				return
			}
		}

		if unsafeUintptr(*argp) {
			return
		}

		if wrapper != nil {
			e.wrapExpr(pos, argp, init, call, wrapper)
		}
	}

	// 为了更好地逃逸，请剥下任何切片文字，并分析它们。例如：
	// 
	// go F（[]int{a，b}）
	// 
	// 如果F没有逃逸它的参数，那么片可以
	// 分配到新goroutine的堆栈上。
	// 
	// 对于变量函数，编译器已经重写了：
	// 
	// f（a，b，c）
	// 
	// to:
	// 
	// f（[]T{a，b，c}）
	// 
	// 所以我们需要研究切片元素，以正确处理uintptr（ptr）
	// 类syscall函数的参数。
	if arg := *argp; arg.Op() == ir.OSLICELIT {
		list := arg.(*ir.CompLitExpr).List
		for i := range list {
			el := &list[i]
			if list[i].Op() == ir.OKEY {
				el = &list[i].(*ir.KeyExpr).Value
			}
			visit(arg.Pos(), el)
		}
	} else {
		visit(call.Pos(), argp)
	}
}

// wrapExpr用临时变量副本替换*exprp。如果包装
// 为非nil，则将捕获变量以在该
// 函数中使用。
func (e *escape) wrapExpr(pos src.XPos, exprp *ir.Node, init *ir.Nodes, call ir.Node, wrapper *ir.Func) *ir.Name {
	tmp := e.copyExpr(pos, *exprp, init, e.curfn, true)

	if wrapper != nil {
		// 当前表示“defer i.M（）”，如果i为零，则在defer语句的
		// 点处恐慌，而不是在调用deferred函数时恐慌。我们需要在包装外进行零检查。
		if call.Op() == ir.OCALLINTER && exprp == &call.(*ir.CallExpr).X.(*ir.SelectorExpr).X {
			check := ir.NewUnaryExpr(pos, ir.OCHECKNIL, ir.NewUnaryExpr(pos, ir.OITAB, tmp))
			init.Append(typecheck.Stmt(check))
		}

		e.oldLoc(tmp).captured = true

		tmp = ir.NewClosureVar(pos, wrapper, tmp)
	}

	*exprp = tmp
	return tmp
}

// copyExpr在fn中创建并返回一个新的临时变量；
// 将语句附加到init以声明并初始化到expr；
// 如果analyze为true，则escape分析数据流。
func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes, fn *ir.Func, analyze bool) *ir.Name {
	if ir.HasUniquePos(expr) {
		pos = expr.Pos()
	}

	tmp := typecheck.TempAt(pos, fn, expr.Type())

	stmts := []ir.Node{
		ir.NewDecl(pos, ir.ODCL, tmp),
		ir.NewAssignStmt(pos, tmp, expr),
	}
	typecheck.Stmts(stmts)
	init.Append(stmts...)

	if analyze {
		e.newLoc(tmp, false)
		e.stmts(stmts)
	}

	return tmp
}

// tagHole返回一个洞，用于计算传递给param的参数。
// ks应该包含代表函数
// 被调用方结果流动的孔。fn是静态已知的被调用函数，
// 如果有的话。
func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
	// 如果这是一个动态调用，我们不能依赖param。笔记
	if fn == nil {
		return e.heapHole()
	}

	if e.inMutualBatch(fn) {
		return e.addr(ir.AsNode(param.Nname))
	}

	// 调用以前标记的函数。

	var tagKs []hole

	esc := parseLeaks(param.Note)
	if x := esc.Heap(); x >= 0 {
		tagKs = append(tagKs, e.heapHole().shift(x))
	}

	if ks != nil {
		for i := 0; i < numEscResults; i++ {
			if x := esc.Result(i); x >= 0 {
				tagKs = append(tagKs, ks[i].shift(x))
			}
		}
	}

	return e.teeHole(tagKs...)
}
